<?php
/**
 * The Google Checkout Server Package
 * 
 * This package contains all of the classes necessary to respond to 
 * notifications sent by Google to your purchasing system. This system is 
 * intended as a base class for your own implementation and business logic. 
 * This set of base classes marshalls the request into a set of objects, 
 * and prepares a response for you to modify.
 *
 * This library is free software; you can redistribute it and/or modify it 
 * under the terms of the BSD License.
 *
 * This library is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 *
 * @package GoogleCheckoutServer
 * @author Byrne Reese <byrne@majordojo.com>
 * @copyright 2006-2008, Byrne Reese.
 */

/**
 * @ignore
 */
require_once("Util.php");
require_once("Log.php");
require_once("HTTP/Request.php");
require_once("Order.php");

/**
 * This value is used to instruct Google to initiate charges to the purchaser's
 * credit card.
 */
define("CHARGE", 2);
/**
 * This value is used to instruct Google not to charge the purchaser's credit card.
 */
define("DONTCHARGE", 1);
/**
 * This value is used to instruct Google not to charge the purchaser's credit card
 * YET.
 */
define("WAIT", 0);

/**
 * The GoogleCheckoutServer class provides the basic capability of 
 * marshalling a request and response for the server you intend to implement. 
 *
 * The server you implement, depending upon whether you elect to use Google 
 * Checkout's XML or HTML based protocol, will call one of two methods on this
 * class:
 *
 * - handlePost()
 * - handleXML()
 *
 * Example:
 *
 *   require("MyGoogleCheckoutServer.php");
 *   $input = $HTTP_RAW_POST_DATA;
 *   $server = new MyGoogleCheckoutServer();
 *   print $server->handleXML($input);
 *   // or
 *   $server = new MyGoogleCheckoutServer();
 *   print $server->handlePost();
 * 
 * @package GoogleCheckoutServer
 * @abstract 
 */
abstract class GoogleCheckoutServer {
  private $logger;
  function __construct() {
    $this->logger = &Log::factory('file', CHECKOUT_LOG_FILE , 'GOOGLE_CHECKOUT');
  }

  /**
   * This method is invoked for servers that are intended to receive 
   * notifications from Google Checkout via the HTML based protocol.
   * @return GoogleCheckoutMessage A response message.
   */
  function handlePost() {
    $message = CheckoutHTMLMessageFactory::create();
    return $this->handle($message);
  }

  /**
   * This method is invoked for servers that are intended to receive 
   * notifications from Google Checkout via the XML based protocol. This 
   * method requires that you  preprocess content from PHP's 
   * $HTTP_RAW_POST_DATA and pass it in yourself.
   *
   * @param string The raw XML request message.
   * @return GoogleCheckoutMessage A response message.
   */
  function handleXML($request) {
    $message = CheckoutXMLMessageFactory::create($request);
    return $this->handle($message);
  }

  /**
   * This private method takes as input a single GoogleCheckoutMessage
   * object and routes it through the handler designated for that message
   * type.
   *
   * @param GoogleCheckoutMessage The request to be processed.
   * @return string The XML response to return to Google.
   * @access private
   */
  function handle($message) {
    if (is_a($message,'NewOrderNotification')) {
      $response = new NotificationAcknowledgment();
      $this->doNewOrderNotification($message,$response);
    } else if (is_a($message,'RiskInformationNotification')) {
      $response = new NotificationAcknowledgment();
      $this->doRiskInformationNotification($message,$response);
      $this->doCharge($message);
    } else if (is_a($message,'OrderStateChangeNotification')) {
      $response = new NotificationAcknowledgment();
      $this->doOrderStateChangeNotification($message,$response);
      $this->doCharge($message);
    } else if (is_a($message,'ChargeAmountNotification')) {
      $response = new NotificationAcknowledgment();
      $this->doChargeAmountNotification($message,$response);
    } else if (is_a($message,'MerchantCalculationCallback')) {
      $response = new MerchantCalculationResults();
      $this->doMerchantCalculationCallback($message,$response);
    } else {
      die("Unknown message type");
    }
    return $response->toXML();
  }

  /**
   * This is the base implementation for processing a charge request. It
   * calls readyToChargeOrder, which queries any subclasses to see if the
   * current order to ready to be charged on the card. This allows 
   * developers to specify their own policy around who and what to charge.
   * This method will then perform the proper action based upon the response
   * from readyToChargeOrder, e.g. send the charge order notification back
   * to Google.
   * 
   * @param GoogleCheckoutMessage A request message.
   * @access private
   */
  function doCharge($message) {
    $do = $this->readyToChargeOrder($message->orderNumber());
    if ($do == CHARGE) {
      $this->logger->log("Ok to charge order ".$message->orderNumber().", sending notification.");
      $this->sendChargeOrderNotification($message->orderNumber());
    } else if ($do == DONTCHARGE) {
      $this->logger->log("Charge rejected. Not issuing command.");
      // do nothing? sendCancelOrderNotification?
    } else if ($do == WAIT) {
      $this->logger->log("Charge not ready to be processed - waiting...");
      // do nothing
    } else {
      // should never happen
    }
  }
  
  /**
   * This method simply constructs an acknowledgement to tell Google, "yes
   * we got the message."
   *
   * @param GoogleCheckoutMessage A request message.
   * @return The raw XML of a notification acknowledgement.
   * @access private
   */
  function handleNewOrder($message) {
    $ack = new NotificationAcknowledgment();
    return $ack;
  }

  /**
   * This method sends a charge-order-notification message to Google
   * instructing Google Checkout to initiate the credit card transaction.
   *
   * @param string A Google Checkout order number.
   * @access private
   */
  public function sendChargeOrderNotification($orderNumber) {
    $amount = $this->getOrderAmount($orderNumber);
    $this->logger->log("Issuing charge order command for $amount.");
    $msg = new ChargeOrderNotification($orderNumber,$amount);
    $client = new GoogleCheckoutClient();
    $this->logger->log("Sending: ".$msg->toXML());
    $response = $client->send($msg->toXML());
    $this->logger->log("Response: $response");
  }

  /**
   * This method sends a deliver order notification to Google to change
   * the status of the item to DELIVERED. You may optionally indicate 
   * if you want the purchaser to be notified via email.
   *
   * @param string An Google Checkout order number.
   * @param boolean Whether or not to send an email to the purchaser
   * @access private
   */
  public function sendDeliverOrderNotification($orderNumber,$email) {
    $this->logger->log("Issuing archive order command.");
    $msg = new DeliverOrderNotification($orderNumber,$email);
    $client = new GoogleCheckoutClient();
    $this->logger->log("Sending: ".$msg->toXML());
    $response = $client->send($msg->toXML());
    $this->logger->log("Response: $response");
  }

  /**
   * Sends a message to Google to instruct them to archive the designated
   * order and to remove it from your checkout inbox.
   *
   * @param string An Google Checkout order number.
   * @access private
   */
  public function sendArchiveOrderNotification($orderNumber) {
    $this->logger->log("Issuing archive order command.");
    $msg = new ArchiveOrderNotification($orderNumber);
    $client = new GoogleCheckoutClient();
    $this->logger->log("Sending: ".$msg->toXML());
    $response = $client->send($msg->toXML());
    $this->logger->log("Response: $response");
  }

  /**
   * This abstract method must be implemented by a sub-class. It is
   * responsible for processing in some way a new order placed by a 
   * customer. 
   * 
   * @param NewOrderNotification A request message.
   * @param GoogleCheckoutMessage A response message.
   * @abstract
   */
  abstract protected function doNewOrderNotification($req,$resp);

  /**
   * This abstract method must be implemented by a sub-class. It is
   * responsible for processing the change in status of an order.
   * 
   * @param OrderStateChangeNotification A request message.
   * @param GoogleCheckoutMessage A response message.
   * @abstract
   */
  abstract protected function doOrderStateChangeNotification($req,$resp);

  /**
   * This abstract method must be implemented by a sub-class. It is
   * responsible for processing a risk notification from Google.
   * 
   * @param RiskInformationNotification A request message.
   * @param GoogleCheckoutMessage A response message.
   * @abstract
   */
  abstract protected function doRiskInformationNotification($req,$resp);

  /**
   * This abstract method must be implemented by a sub-class. It is
   * responsible for processing the message indicating that a specific 
   * amount has been charged to the customer.
   * 
   * @param ChargeAmountNotification A request message.
   * @param GoogleCheckoutMessage A response message.
   * @abstract
   */
  abstract protected function doChargeAmountNotification($req,$resp);

  /**
   * This abstract method must be implemented by a sub-class. It is
   * responsible for calculating any additional merchant charge or
   * order changes, like discount codes, gift certificates, etc.
   * 
   * @param MerchantCalculationCallback A request message.
   * @param GoogleCheckoutMessage A response message.
   * @abstract
   */
  abstract protected function doMerchantCalculationCallback($req,$resp);

  /**
   * This abstract method must be implemented by a sub-class. It is
   * responsible for processing in some way a new order placed by a 
   * customer. 
   * 
   * @param Order 
   * @return Either: CHARGE, DONTCHARGE, or WAIT
   * @abstract
   */
  abstract protected function readyToChargeOrder($order);

  /**
   * This abstract method must be implemented by a sub-class. It is
   * responsible for calculating and returning the actual order amount, after
   * all discounts and gift certificates have been applied.
   * 
   * @param Order 
   * @return float The amount associated with the related order.
   * @abstract
   */
  abstract protected function getOrderAmount($order);
}

/**
 * This class inspects the global $_POST variable, a variable managed by PHP
 * that contains all of the posted name value pairs, and constructs and 
 * returns a valid GoogleCheckoutMessage will all of the posted content
 * deserialized properly.
 *
 * Users do not need to interact with this class directly. The
 * GoogleCheckoutServer will interface with the proper message factory for
 * you automatically.
 *
 * @package GoogleCheckoutServer
 */
class CheckoutHTMLMessageFactory {
  /**
   * @return GoogleCheckoutMessage A message corresponding to the posted 
   * content
   * @throws Exception Throws an exception if the message posted in not 
   * recognized.
   */
  public static function create() {
    global $_POST;
    if ($_POST['_type'] == 'new-order-notification') {
      return NewOrderNotification::fromPost($_POST);
    } else if ($_POST['_type'] == 'order-state-change-notification') {
      return OrderStateChangeNotification::fromPost($_POST);
    } else if ($_POST['_type'] == 'risk-information-notification') {
      return RiskInformationNotification::fromPost($_POST);
    } else if ($_POST['_type'] == 'charge-amount-notification') {
      return ChargeAmountNotification::fromPost($_POST);
    } else if ($_POST['_type'] == 'merchant-calculation-callback') {
      return MerchantCalculationCallback::fromPost($_POST);
    } else {
      throw new Exception("Unknown message type: " . $_POST['_type']);
    }
  }
}

/**
 * This class is responsible for parsing the XML posted by Google and then
 * to construct and  return a valid GoogleCheckoutMessage will all of the 
 * posted content deserialized properly.
 *
 * Users do not need to interact with this class directly. The
 * GoogleCheckoutServer will interface with the proper message factory for
 * you automatically.
 *
 * @package GoogleCheckoutServer
 */
class CheckoutXMLMessageFactory {
  /**
   * This method takes as input raw XML and then returns the proper serialized
   * object corresponding to the input.
   * @param string The raw XML that was posted by Google.
   * @return GoogleCheckoutMessage A message corresponding to the posted 
   * content
   * @throws Exception Throws an exception if the message posted in not 
   * recognized.
   */
  public static function create($xml) {
    $parser = xml_parser_create();
    xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
    xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
    xml_parse_into_struct($parser, $xml, $tags);
    xml_parser_free($parser);
    
    $elements = array();  // the currently filling [child] XmlElement array
    $stack = array();
    foreach ($tags as $tag) {
      $index = count($elements);
      if ($tag['type'] == "complete" || $tag['type'] == "open") {
	$elements[$index] = new XmlElement;
	$elements[$index]->name = $tag['tag'];
	$elements[$index]->attributes = $tag['attributes'];
	$elements[$index]->content = $tag['value'];
	if ($tag['type'] == "open") {  // push
	  $elements[$index]->children = array();
	  $stack[count($stack)] = &$elements;
	  $elements = &$elements[$index]->children;
	}
      }
      if ($tag['type'] == "close") {  // pop
	$elements = &$stack[count($stack) - 1];
	unset($stack[count($stack) - 1]);
      }
    }
    $root = $elements[0];  // the single top-level element
    if ($root->name == 'new-order-notification') {
      return new NewOrderNotification($root);
    } else if ($root->name == 'order-state-change-notification') {
      return new OrderStateChangeNotification($root);
    } else if ($root->name == 'risk-information-notification') {
      return new RiskInformationNotification($root);
    } else if ($root->name == 'charge-amount-notification') {
      return new ChargeAmountNotification($root);
    } else if ($root->name == 'merchant-calculation-callback') {
      return new MerchantCalculationCallback($root);
    } else {
      throw new Exception("Unknown message type: " . $root->name);
    }
  } 
}

/**
 * This is a helper class to properly serialize tracking data for Google
 * Checkout.
 * 
 * @package GoogleCheckoutServer
 */
class TrackingData {
  /**
   * The shipping provider or carier. UPS, FedEx, etc. Consult Google for allowable
   * values.
   * @var string
   */
  public $carrier;
  /**
   * The tracking number provided by the carrier for the shipment.
   * @var string
   */
  public $trackingNumber;
  /**
   * @param string Valid values for the carrier tag are DHL, FedEx, UPS, USPS
   * and Other.
   * @param string A tracking number.
   */
  function __construct($c,$t) {
    $this->carrier = $c;
  }
  /**
   * Serialize the message into XML.
   * @return string The message in XML.
   */
  public function toXML() {
    $xml = '<tracking-data>'."\n";
    $xml .= '  <carrier>'.$this->carrier.'</carrier>'."\n";
    $xml .= '  <tracking-data>'.$this->reason.'</tracking-number>'."\n";
    $xml .= '</tracking-data>'."\n";
    return $xml;
  }
}

/**
 * This the base class for all messages received from Google Checkout. It
 * contains the data that is found most commonly among those messages.
 *
 * @package GoogleCheckoutServer
 */
class CheckoutMessage {
  protected $data;
  protected $orderNumber;
  protected $timestamp;
  protected $serialNumber;
  /**
   * The serial number contained within the message.
   * @return string The serial number.
   */
  function serialNumber() { return $this->serialNumber; }
  /**
   * The timestamp contained within the message.
   * @return timestamp The timestamp in Unix epoch.
   */
  function timestamp() { return $this->timestamp; }
  /**
   * The Google Checkout Order Number contained within the message.
   * @return string The order number.
   */
  function orderNumber() { return $this->orderNumber; }
}

/**
 * A simple helper function to deserialize a call to the Merchant
 * Calculation callback.
 *
 * @package GoogleCheckoutServer
 * @todo extract serial number in fromXML
 */
class MerchantCalculationCallback extends CheckoutMessage {
  /**
   * Converts XML into a proper object.
   * @param XmlElement An XML data structure
   * @return MerchantCalculationCallback
   */
  public static function fromXML($struct) {
    $m = new MerchantCalculationCallback();
    $m->data = $d;
    for ($i = 0; $i < count($d->children); $i++) {
      if ($d->children[$i]->name == "google-order-number") {
        $m->orderNumber = $d->children[$i]->content;
      } else if ($d->children[$i]->name == "timestamp") {
        $m->timestamp = $d->children[$i]->content;
      } 
    }
    $m->orderNumber = $m->order->orderNumber;
    return $m;

  }
  /**
   * Converts an array into a proper object.
   * @param Array An associative array, typically a $_POST array.
   * @return MerchantCalculationCallback
   */
  public static function fromPost($array) {
    $m = new MerchantCalculationCallback();
    $m->serialNumber = $array['serial-number'];
    $m->timestamp = $array['timestamp'];
    $m->order = new Order();
    $m->order->orderNumber = $array['google-order-number'];
    $m->orderNumber = $m->order->orderNumber;
    return $m;
  }
}

/**
 * A notification sent by Google when the state of an order changes.
 * @package GoogleCheckoutServer
 */
class OrderStateChangeNotification extends CheckoutMessage {
  /**
   * Converts XML into a proper object.
   * @param XmlMLElement An XML data structure
   * @return OrderStateChangeNotification
   */
  public static function fromXML($struct) {
    $m = new OrderStateChangeNotification();
    $m->data = $d;
    for ($i = 0; $i < count($d->children); $i++) {
      if ($d->children[$i]->name == "google-order-number") {
        $m->orderNumber = $d->children[$i]->content;
      } else if ($d->children[$i]->name == "timestamp") {
        $m->timestamp = $d->children[$i]->content;
      } 
    }
    return $m;
  }
  /**
   * Converts an array into a proper object.
   * @param Array An associative array, typically a $_POST array.
   * @return OrderStateChangeNotification
   */
  public static function fromPost($array) {
    $m = new OrderStateChangeNotification();
    $m->serialNumber = $array['serial-number'];
    $m->timestamp = $array['timestamp'];
    $m->order = new Order();
    $m->order->orderNumber = $array['google-order-number'];
    $m->orderNumber = $m->order->orderNumber;
    $m->fulfillmentState = $array['new-fulfillment-order-state'];
    $m->financialState = $array['new-financial-order-state'];
    $m->previousFulfillmentState = $array['previous-fulfillment-order-state'];
    $m->previousFinancialState = $array['previous-financial-order-state'];
    return $m;
  }
  /**
   * @return string The fullfilment state of the order.
   */
  function fulfillmentState() { return $this->fulfillmentState; }
  /**
   * @return string The financial state of the order.
   */
  function financialState() { return $this->financialState; }
  /**
   * @return string The <em>previous</em> fullfilment state of the order.
   */
  function previousFulfillmentState() { return $this->previousFulfillmentState; }
  /**
   * @return string The <em>previous</em> financial state of the order.
   */
  function previousFinancialState() { return $this->previousFinancialState; }
}

/**
 * A message sent by Google letting you know they charged the user.
 * 
 * Google Checkout sends a <charge-amount-notification> after successfully charging
 * a customer for an order. Google Checkout will attempt to charge a customer if 
 * you send a <charge-order> order processing command requesting a charge. You 
 * can also instruct Google to automatically charge customers when they submit new
 * orders. To set this preference, log in to your Checkout account and click the 
 * Settings tab. Then click the Preferences link in the menu on the left side of 
 * the page. Finally, select the option to "Automatically authorize and charge 
 * the buyer's credit card."
 * 
 * @package GoogleCheckoutServer
 */
class ChargeAmountNotification extends CheckoutMessage {
  private $latestChargeAmount;
  private $totalChargeAmount;
  /**
   * Converts XML into a proper object.
   * @param XmlElement An XML data structure
   * @return ChargeAmountNotification
   * @todo verify
   */
  public static function fromXML($d) {
    $m = new ChargeAmountNotification();
    $m->data = $d;
    for ($i = 0; $i < count($d->children); $i++) {
      if ($d->children[$i]->name == "google-order-number") {
        $m->orderNumber = $d->children[$i]->content;
      } else if ($d->children[$i]->name == "timestamp") {
        $m->timestamp = $d->children[$i]->content;
      } 
    }
    $m->latestChargeAmount = $d->children[2]->content;
    $m->totalChargeAmount = $d->children[3]->content;
    return $m;
  }
  /**
   * Converts an array into a proper object.
   * @param Array An associative array, typically a $_POST array.
   * @return ChargeAmountNotification
   */
  public static function fromPost($array) {
    $m = new ChargeAmountNotification();
    $m->serialNumber = $array['serial-number'];
    $m->timestamp = $array['timestamp'];
    $m->order = new Order();
    $m->order->orderNumber = $array['google-order-number'];
    $m->latestChargeAmount = $array['latest-charge-amount'];
    $m->totalChargeAmount = $array['total-charge-amount'];
    return $m;
  }
  /**
   * @return float The amount most recently charged for a particular order.
   */
  function latestChargeAmount() { return $this->latestChargeAmount; }
  /**
   * @return float The total amount charged for a particular order.
   */
  function totalChargeAmount() { return $this->totalChargeAmount; }
}

/**
 * A message sent by Google to let you know they have refunded the purchaser.
 * 
 * Google Checkout sends a <refund-amount-notification> after successfully 
 * executing a <refund-order> order processing command. You should not assume that
 * Google has granted a refund until you receive this notification. Typically, 
 * this notification is sent within seconds of the corresponding <refund-order> 
 * request.
 *
 * @package GoogleCheckoutServer
 */
class RefundAmountNotification extends CheckoutMessage {
  private $totalRefundAmount;
  /**
   * Converts XML into a proper object.
   * @param XmlElement An XML data structure
   * @return RefundAmountNotification
   */
  public static function fromXML($d) {
    $m = new RefundAmountNotification();
    $m->data = $d;
    for ($i = 0; $i < count($d->children); $i++) {
      if ($d->children[$i]->name == "google-order-number") {
        $m->orderNumber = $d->children[$i]->content;
      } else if ($d->children[$i]->name == "timestamp") {
        $m->timestamp = $d->children[$i]->content;
      } 
    }
    $m->totalRefundAmount = $d->children[2]->content;
    return $m;
  }
  /**
   * Converts an array into a proper object.
   * @param Array An associative array, typically a $_POST array.
   * @return RefundAmountNotification
   */
  public static function fromPost($array) {
    $m = new RefundAmountNotification();
    $m->serialNumber = $array['serial-number'];
    $m->timestamp = $array['timestamp'];
    $m->order = new Order();
    $m->order->orderNumber = $array['google-order-number'];
    $m->totalRefundAmount = $array['total-refund-amount'];
    return $m;
  }
  /**
   * @return float The amount refunded.
   */
  public function totalRefundAmount() { return $this->totalRufundAmount; }
}

/**
 * A message sent by Google letting you know critical information about the
 * purchaser.
 * 
 * Google Checkout sends a risk information notification after completing its 
 * risk analysis on a new order. A risk-information-notification includes 
 * financial information such as the customer's billing address, a partial 
 * credit card number and other values that help you verify that an order is 
 * not fraudulent.
 * 
 * @package GoogleCheckoutServer
 */
class RiskInformationNotification extends CheckoutMessage {
  /**
   * The buyer's billing address. Of type Address.
   * @var Address
   */
  public $buyerBillingAddress;
  /**
   * Converts XML into a proper object.
   * @param XmlElement An XML data structure
   * @return RiskInformationNotification
   */
  public static function fromXML($d) {
    $m = new RiskInformationNotification();
    $m->data = $d;
    for ($i = 0; $i < count($d->children); $i++) {
      if ($d->children[$i]->name == "google-order-number") {
        $m->orderNumber = $d->children[$i]->content;
      } else if ($d->children[$i]->name == "timestamp") {
        $m->timestamp = $d->children[$i]->content;
      } 
    }
    $m->buyerBillingAddress = Address::fromXML($d->children[2]->children[0]);
    $m->ipAddress = $d->children[2]->children[1]->content;
    $m->eligibleForProtection = $d->children[2]->children[2]->content;
    $m->avsResponse= $d->children[2]->children[3]->content;
    $m->cvnResponse = $d->children[2]->children[4]->content;
    $m->partialCCNumber = $d->children[2]->children[5]->content;
    $m->buyerAccountAge = $d->children[2]->children[6]->content;
    return $m;
  }
  /**
   * Converts an array into a proper object.
   * @param Array An associative array, typically a $_POST array.
   * @return RiskInformationNotification
   */
  public static function fromPost($array) {
    $m = new RiskInformationNotification();
    $m->serialNumber = $array['serial-number'];
    $m->timestamp = $array['timestamp'];
    $m->orderNumber = $array['google-order-number'];
    $m->order = new Order();
    $m->order->orderNumber = $m->order->orderNumber;
    $m->buyerBillingAddress = Address::fromPost($array,'risk-information_billing-address');
    $m->ipAddress = $array['risk-information_ip-address'];
    $m->eligibleForProtection = $array['risk-information_eligible-for-protection'];
    $m->avsResponse = $array['risk-information_avs-response'];
    $m->cvnResponse = $array['risk-information_cvn-response'];
    $m->partialCCNumber = $array['risk-information_partial-cc-number'];
    $m->buyerAccountAge = $array['risk-information_buyer-account-age'];
    return $m;
  }
  /**
   * @return string The IP address of the purchaser.
   */
  function ipAddress() { return $this->ipAddress; }
  /**
   * @return boolean Whether the purchaser is eligible for protection.
   */
  function eligibleForProtection() { return $this->eligibleForProtection; }
  /**
   * @return string Response from the Address Verification System.
   */
  function avsResponse() { return $this->avsResponse; }
  /**
   * Possible values for this tag are:
   * M - CVN match
   * N - No CVN match
   * U - CVN not available
   * E - CVN error
   * 
   * @return string  The credit verification value for the order.
   */
  function cvnResponse() { return $this->cvnResponse; }
  /**
   * @return string The last four digits of the purchaser's credit card.
   */
  function partialCCNumber() { return $this->partialCCNumber; }
  /**
   * @return string The age, in days, of the buyer's Google Checkout account.
   */
  function buyerAccountAge() { return $this->buyerAccountAge; }
}

/**
 * The element encapsulates verbatim the shopping cart as it was submitted to 
 * Google from the seller's web site.
 * 
 * @package GoogleCheckoutServer
 */
class ShoppingCartElement {
  /**
   * An array of private data passed to Google Checkout from the seller.
   * @var Array
   */
  public $privateData = array();
  /**
   * Converts XML into a proper object.
   * @param XMLElement An XML data structure
   * @return ShoppingCartElement
   */
  public static function fromXML($d) {
    $m = new ShoppingCartElement();
    for ($i = 0; $i < count($d->children); $i++) {
      if ($d->children[$i]->name == "google-order-number") {
        $m->orderNumber = $d->children[$i]->content;
      } else if ($d->children[$i]->name == "timestamp") {
        $m->timestamp = $d->children[$i]->content;
      } 
      if ($struct->children[$i]->name == "merchant-private-data") {
	for ($j = 0; $j < count($d->children[$i]->children); $j++) {
	  $k = $d->children[$i]->children[$j]->name;
	  $v = $d->children[$i]->children[$j]->content;
	  $this->privateData[$k] = $v;
	}
      }
    }
  }
  /**
   * Converts an array into a proper object.
   * @param Array An associative array, typically a $_POST array.
   * @return ShoppingCartElement
   */
  public static function fromPost($array) {
    /* TODO - verify */
    $m = new ShoppingCartElement();
    $m->privateData = $array['shopping-cart.merchant-private-data'];
    return $m;
  }
}

/**
 * A message sent by Google letting you know of a new order placed by a purchaser.
 *
 * Google sends a new order notification when a buyer places an order through 
 * Google Checkout. This notification contains the following information:
 * - the shopping cart as Google received it
 * - order-adjustment, taxes, coupons, gift certificates, etc
 * - buyer billing and shipping address
 * - order total
 * - buyer id
 *
 * @package GoogleCheckoutServer
 */
class NewOrderNotification extends CheckoutMessage {
  /**
   * An XML struct of the the entire message.
   * @var XmlElement
   */
  public $data;
  /**
   * The shopping cart as it was posted to Google.
   * @var ShoppingCartElement
   */
  public $shoppingCart;
  /**
   * The purchaser's marketing preferences, e.g. can you contact them via email?
   * @var MarketingPreferences
   */
  public $buyerMarketingPreferences;
  /**
   * The purchaser's order.
   * @var Order
   */
  public $order;
  /**
   * Converts XML into a proper object.
   * @param XmlElement An XML data structure
   * @return NewOrderNotification
   */
  public static function fromXML($d) {
    $this->data = $d;
    for ($i = 0; $i < count($d->children); $i++) {
      if ($d->children[$i]->name == "google-order-number") {
        $this->orderNumber = $d->children[$i]->content;
      } else if ($d->children[$i]->name == "timestamp") {
        $this->timestamp = $d->children[$i]->content;
      } 
    }
    $this->order = new Order();
    $this->order->orderNumber = $this->orderNumber();
    for ($i = 0; $i < count($d->children); $i++) {
      if ($d->children[$i]->name == "buyer-shipping-address") {
	$this->order->shippingAddress = Address::fromXML($d->children[$i]);
      } else if ($d->children[$i]->name == "buyer-billing-address") {
	$this->order->billingAddress = Address::fromXML($d->children[$i]);
      } else if ($d->children[$i]->name == "buyer-marketing-preferences") {
	$this->buyerMarketingPreferences = new MarketingPreferences($d->children[$i]);
      } else if ($d->children[$i]->name == "order-total") {
	$this->order->orderTotal = $d->children[$i]->content;
      } else if ($d->children[$i]->name == "fulfillment-order-state") {
	$this->order->fulfillmentState = $d->children[$i]->content;
      } else if ($d->children[$i]->name == "financial-order-state") {
	$this->order->financialState = $d->children[$i]->content;
      } else if ($d->children[$i]->name == "buyer-id") {
	$this->order->buyerId = $d->children[$i]->content;
      } else if ($d->children[$i]->name == "shopping-cart") {
	$this->shoppingCart = new ShoppingCartElement($d->children[$i]);
      }
    }
  }

  /**
   * Converts an array into a proper object.
   * @param Array An associative array, typically a $_POST array.
   * @return NewOrderNotification
   */
  public static function fromPost($array) {
    $m = new NewOrderNotification();
    $m->serialNumber = $array['serial-number'];
    $m->timestamp = $array['timestamp'];
    $m->orderNumber = $array['google-order-number'];
    $m->order = new Order();
    $m->order->orderNumber = $m->orderNumber;
    $m->order->orderTotal = $array['order-total'];
    $m->order->fulfillmentState = $array['fulfillment-order-state'];
    $m->order->financialState = $array['financial-order-state'];
    $m->order->buyerId = $array['buyer-id'];
    $m->order->shippingAddress = Address::fromPost($array,'buyer-shipping-address');
    $m->order->billingAddress = Address::fromPost($array,'buyer-billing-address');
    $m->buyerMarketingPreferences = MarketingPreferences::fromPost($array);
    $m->shoppingCart = ShoppingCartElement::fromPost($array);
    return $m;
  }

  /**
   * @return float The order's total amount.
   */
  function orderTotal() { return $this->order->orderTotal; }
  /**
   * @return string The order's current fulfillment state.
   */
  function fulfillmentState() { return $this->order->fulfillmentState; }
  /**
   * @return string The order's current financial state.
   */
  function financialState() { return $this->order->financialState; }
  /**
   * @return int The buyer Id of the purchaser.
   */
  function buyerId() { return $this->order->buyerId; }
}

/**
 * A message you send to Google letting them know you have successfully 
 * received a notification.
 *
 * This message signals to Google that your system has successfully received
 * and processed any notification sent to you via the Notification API. This
 * message must be sent synchronously in response to the notification you
 * receive.
 *
 * @package GoogleCheckoutServer
 */
class NotificationAcknowledgment extends CheckoutMessage {
  function __construct() { }
  /**
   * Serialize the notification into XML.
   * @return string The message formated as XML.
   */
  function toXML() {
    $xml  = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
    $xml .= '<notification-acknowledgment xmlns="http://checkout.google.com/schema/2" />';
    return $xml;
  }
}

/**
 * A message you send to Google asking them to charge the purchaser. 
 *
 * The <charge-order> command instructs Google Checkout to charge the buyer 
 * for a particular order. After an order reaches the CHARGEABLE order state,
 * you have seven days ? 168 hours ? to capture funds by issuing a 
 * <charge-order> command. You may issue a <charge-order> command for an 
 * order that is in any of the following financial order states:
 * - REVIEWING
 * - CHARGEABLE
 * - CHARGED (if the order has only been partially charged) 
 *
 * @package GoogleCheckoutServer
 */
class ChargeOrderNotification extends CheckoutMessage {
  protected $orderNumber;
  protected $orderTotal;
  /**
   * @param string The Google Checkout order number.
   * @param float The total amount of the order.
   */
  function __construct($o,$a) {
    $this->orderNumber = $o;
    $this->orderTotal = $a;
  }
  /**
   * Serialize the notification into XML.
   * @return string The message formated as XML.
   */
  public function toXML() {
    $xml  = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
    $xml .= '<charge-order xmlns="http://checkout.google.com/schema/2" google-order-number="'.$this->orderNumber.'">'."\n";
    $xml .= '  <amount currency="USD">'.$this->orderTotal.'</amount>'."\n";
    $xml .= '</charge-order>'."\n";
    return $xml;
  }
}

/**
 * A message you sent to Google asking them to refund the purchaser.
 *
 * The <refund-order> command instructs Google Checkout to refund the buyer 
 * for a particular order. You may issue a <refund-order> command after an 
 * order has been charged and is in the CHARGED financial order state. 
 *
 * @package GoogleCheckoutServer
 */
class RefundOrderNotification extends CheckoutMessage {
  protected $orderNumber;
  protected $amount;
  protected $comment;
  protected $reason;
  /**
   * @param string The Google Checkout order number.
   * @param float The amount to refund.
   * @param string A comment about the refund.
   * @param string The reason for the refund.
   */
  function __construct($o,$a,$c,$r) {
    $this->orderNumber = $o;
    $this->amount = $a;
    $this->comment = $c;
    $this->reason = $r;
  }
  /**
   * Serialize the notification into XML.
   * @return string The message formated as XML.
   */
  public function toXML() {
    $xml  = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
    $xml .= '<refund-order xmlns="http://checkout.google.com/schema/2" google-order-number="'.$this->orderNumber.'">'."\n";
    $xml .= '  <amount currency="USD">'.$this->orderTotal.'</amount>'."\n";
    $xml .= '  <comment>'.$this->comment.'</comment>'."\n";
    $xml .= '  <reason>'.$this->reason.'</reason>'."\n";
    $xml .= '</refund-order>'."\n";
    return $xml;
  }
}

/**
 * A message you send to Google asking them to archive the order.
 *
 * The <archive-order> command instructs Google Checkout to remove an order 
 * from your Merchant Center Inbox. We recommend that you only archive orders
 * after they have been delivered or canceled.
 *
 * @package GoogleCheckoutServer
 */
class ArchiveOrderNotification extends CheckoutMessage {
  protected $orderNumber;
  /**
   * @param string The Google Checkout order number.
   */
  function __construct($o) {
    $this->orderNumber = $o;
  }
  /**
   * Serialize the notification into XML.
   * @return string The message formated as XML.
   */
  public function toXML() {
    $xml  = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
    $xml .= '<archive-order xmlns="http://checkout.google.com/schema/2" google-order-number="'.$this->orderNumber.'" />'."\n";
    return $xml;
  }
}

/**
 * A message you send to Google asking them to mark the order as delivered.
 *
 * The <deliver-order> command instructs Google Checkout to update an order's
 * fulfillment state from either NEW or PROCESSING to DELIVERED. You would 
 * send this command after the order has been charged and shipped.
 *
 * @package GoogleCheckoutServer
 */
class DeliverOrderNotification extends CheckoutMessage {
  protected $orderNumber;
  protected $sendEmail;
  /**
   * @param string The order number.
   * @param TrackingData Any additional tracking data you have for the order
   * @param boolean Send an email to the user?
   */
  function __construct($o,$td,$send) {
    $this->orderNumber = $o;
    $this->trackingData = $td;
    $this->sendEmail = $send;
  }
  /**
   * Serialize the notification into XML.
   * @return string The message in XML.
   */
  public function toXML() {
    $xml  = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
    $xml .= '<deliver-order xmlns="http://checkout.google.com/schema/2" google-order-number="'.$this->orderNumber.'">'."\n";
    if ($this->trackingData) {
      $xml .= $this->trackingData->toXML();
    }
    $xml .= '  <send-email>'.$this->sendEmail ? 'true' : 'false'.'</send-email>'."\n";
    $xml .= '</deliver-order>'."\n";
    return $xml;
  }
}

/**
 * A message you send to Google instructing them to cancel the order.
 *
 * The <cancel-order> command instructs Google Checkout to cancel a 
 * particular order. If the customer has already been charged, you must 
 * refund the customer's money before you can cancel the order. You may 
 * issue a <cancel-order> command for an order that is in either of the 
 * following financial order states:
 * - CHARGEABLE
 * - PAYMENT_DECLINED 
 *
 * @package GoogleCheckoutServer
 */
class CancelOrderNotification extends CheckoutMessage {
  protected $orderNumber;
  protected $comment;
  protected $reason;
  /**
   * @param string The Google Checkout order number.
   * @param string A comment about the canceled order.
   * @param string The reason the order is being cancelled.
   */
  function __construct($o,$c,$r) {
    $this->orderNumber = $o;
    $this->comment = $c;
    $this->reason = $r;
  }
  /**
   * Serialize the notification into XML.
   * @return string The message formated as XML.
   */
  public function toXML() {
    $xml  = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
    $xml .= '<cancel-order xmlns="http://checkout.google.com/schema/2" google-order-number="'.$this->orderNumber.'">'."\n";
    $xml .= '  <comment>'.$this->comment.'</comment>'."\n";
    $xml .= '  <reason>'.$this->reason.'</reason>'."\n";
    $xml .= '</cancel-order>'."\n";
    return $xml;
  }
}

/** 
 * A message you send to Google asking them to re-authorize the purchaser's credit
 * card.
 *
 * The <authorize-order> command instructs Google Checkout to explicitly 
 * reauthorize a customer's credit card for the uncharged balance of an order
 * to verify that funds for the order are available. You may issue an 
 * <authorize-order> command for an order that is in either of the following 
 * financial order states:
 * - CHARGEABLE
 * - CHARGED (You could reauthorize an order that had been partially charged.)
 *
 * @package GoogleCheckoutServer
 */
class AuthorizeOrderNotification extends CheckoutMessage {
  protected $orderNumber;
  /**
   * @param string The Google Checkout order number.
   */
  function __construct($o) {
    $this->orderNumber = $o;
  }
  /**
   * Serialize the notification into XML.
   * @return string The message formated as XML.
   */
  public function toXML() {
    $xml  = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
    $xml .= '<authorize-order xmlns="http://checkout.google.com/schema/2" google-order-number="'.$this->orderNumber.'" />'."\n";
    return $xml;
  }
}

/**
 * @todo document
 * @package GoogleCheckoutServer
 */
class MerchantCalculationResults extends CheckoutMessage {
  private $totalTax;
  private $shippingRate;
  private $shippable;
  private $discountXML;
  private $giftXML;
  /**
   * @param string The Google Checkout order number.
   * @param float The total order amount.
   */
  function __construct($o,$a) {
    $this->orderNumber = $o;
    $this->orderTotal = $a;
  }
  function processDiscount($code,$amount,$message) {
    $xml  = "<coupon-result>\n";
    $xml .= "  <valid>true</valid>\n";
    $xml .= "  <code>$code</code>\n";
    $xml .= "  <calculated-amount currency=\"USD\">$amount</calculated-amount>\n";
    $xml .= "  <message>$message</message>\n";
    $xml .= "</coupon-result>\n";
    $this->discountXML = $xml;
  }
  function rejectDiscount($code,$message) {
    $xml  = "<coupon-result>\n";
    $xml .= "  <valid>false</valid>\n";
    $xml .= "  <code>$code</code>\n";
    $xml .= "  <calculated-amount currency=\"USD\">0.00</calculated-amount>\n";
    $xml .= "  <message>$message</message>\n";
    $xml .= "</coupon-result>\n";
    $this->discountXML = $xml;
  }
  function processGiftCertificate($code,$amount,$message) {
    $xml  = "<gift-certificate-result>\n";
    $xml .= "  <valid>true</valid>\n";
    $xml .= "  <code>$code</code>\n";
    $xml .= "  <calculated-amount currency=\"USD\">$amount</calculated-amount>\n";
    $xml .= "  <message>$message</message>\n";
    $xml .= "</gift-certificate-result>\n";
    $this->giftXML = $xml;
  }
  function rejectGiftCertificate($code,$message) {
    $xml  = "<gift-certificate-result>\n";
    $xml .= "  <valid>false</valid>\n";
    $xml .= "  <code>$code</code>\n";
    $xml .= "  <calculated-amount currency=\"USD\">0.00</calculated-amount>\n";
    $xml .= "  <message>$message</message>\n";
    $xml .= "</gift-certificate-result>\n";
    $this->giftXML = $xml;
  }
  /**
   * Serialize the notification into XML.
   * @return string The message formated as XML.
   */
  public function toXML() {
    $xml  = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
    $xml .= '<merchant-calculation-results xmlns="http://checkout.google.com/schema/2">'."\n";
    $xml .= "  <results>\n";
/*
    $xml .= "    <result shipping-name=\"SuperShip\" address-id=\"739030698069958\">
      <shipping-rate currency="USD">7.03</shipping-rate>
      <shippable>true</shippable>

      <total-tax currency="USD">14.67</total-tax>
      <merchant-code-results>
        <coupon-result>
          <valid>true</valid>

          <code>FirstVisitCoupon</code>
          <calculated-amount currency="USD">5.00</calculated-amount>
          <message>Congratulations! You saved $5.00 on your first visit!</message>
        </coupon-result>

        <gift-certificate-result>
          <valid>true</valid>
          <code>GiftCert012345</code>
          <calculated-amount currency="USD">10.00</calculated-amount>

          <message>You used your Gift Certificate!</message>
        </gift-certificate-result>
      </merchant-code-results>
    </result>
    <result shipping-name="UPS Ground" address-id="739030698069958">

      <shipping-rate currency="USD">5.56</shipping-rate>
      <shippable>true</shippable>
      <total-tax currency="USD">14.67</total-tax>

      <merchant-code-results>
        <coupon-result>
          <valid>true</valid>
          <code>FirstVisitCoupon</code>
          <calculated-amount currency="USD">5.00</calculated-amount>

          <message>Congratulations! You saved $5.00 on your first visit!</message>
        </coupon-result>
        <gift-certificate-result>
          <valid>true</valid>
          <code>GiftCert012345</code>

          <calculated-amount currency="USD">10.00</calculated-amount>
          <message>You used your Gift Certificate!</message>
        </gift-certificate-result>
      </merchant-code-results>

    </result>
    <result shipping-name="SuperShip" address-id="421273450774618">
      <shipping-rate currency="USD">9.66</shipping-rate>
      <shippable>true</shippable>

      <total-tax currency="USD">17.57</total-tax>
      <merchant-code-results>
        <coupon-result>
          <valid>true</valid>

          <code>FirstVisitCoupon</code>
          <calculated-amount currency="USD">5.00</calculated-amount>
          <message>Congratulations! You saved $5.00 on your first visit!</message>
        </coupon-result>

        <gift-certificate-result>
          <valid>true</valid>
          <code>GiftCert012345</code>
          <calculated-amount currency="USD">10.00</calculated-amount>

          <message>You used your Gift Certificate!</message>
        </gift-certificate-result>
      </merchant-code-results>
    </result>
    <result shipping-name="UPS Ground" address-id="421273450774618">

      <shipping-rate currency="USD">7.68</shipping-rate>
      <shippable>true</shippable>
      <total-tax currency="USD">17.57</total-tax>

      <merchant-code-results>
        <coupon-result>
          <valid>true</valid>
          <code>FirstVisitCoupon</code>
          <calculated-amount currency="USD">5.00</calculated-amount>

          <message>Congratulations! You saved $5.00 on your first visit!</message>
        </coupon-result>
        <gift-certificate-result>
          <valid>true</valid>
          <code>GiftCert012345</code>

          <calculated-amount currency="USD">10.00</calculated-amount>
          <message>You used your Gift Certificate!</message>
        </gift-certificate-result>
      </merchant-code-results>

    </result>
*/
    $xml .= "  </results>\n";
    $xml .= "</merchant-calculation-results>\n";
    return $xml;
  }
}

/**
 * An address is a class that embodies physical snail mail address. 
 * 
 * Not only is this class a helper class to marshal either an XML struct or an 
 * HTTP POST into a simple PHP data structure, but it also gives you access to the 
 * components of the address for billing and shipping purposes.
 *
 * @package GoogleCheckoutServer
 */
class Address {
  /**
   * The purchaser's email address.
   * @var string
   */ 
  public $email;
  /**
   * The purchaser's address (line 1).
   * @var string
   */
  public $address1;
  /** 
   * The purchaser's address (line 2).
   * @var string
   */
  public $address2;
  /**
   * The purchaser's company name (if any).
   * @var string
   */
  public $companyName;
  /**
   * The purchaser's name, or a contact name.
   * @var string
   */
  public $contactName;
  /**
   * The purchaser's phone number.
   * @var string
   */
  public $phone;
  /**
   * The purchaser's fax number (if any).
   * @var string
   */
  public $fax;
  /**
   * The country code for the purchaser.
   * @var string
   */
  public $countryCode;
  /**
   * The purchaser's city.
   * @var string
   */
  public $city;
  /**
   * The purchasers region or state.
   * @var string
   */
  public $region;
  /**
   * The postal, or zip code of the purchaser.
   * @var string
   */
  public $postalCode;

  /**
   * Converts an array into a proper object.
   *
   * @param Array An associative array, typically a $_POST array.
   * @param string A prefix that proceeds each of the standard element names
   * @return OrderAdjustment 
   */
  public static function fromPost($array,$pre) {
    $a = new Address();
    $a->email = $array[$pre . "_email"];
    $a->address1 = $array[$pre . "_address1"];
    $a->address2 = $array[$pre . "_address2"];
    $a->companyName = $array[$pre . "_company-name"];
    $a->contactName = $array[$pre . "_contact-name"];
    $a->phone = $array[$pre . "_phone"];
    $a->fax = $array[$pre . "_fax"];
    $a->countryCode = $array[$pre . "_country-code"];
    $a->city = $array[$pre . "_city"];
    $a->region = $array[$pre . "_region"];
    $a->postalCode = $array[$pre . "_postal-code"];
    return $a;
  }  

  /**
   * Converts XML into a proper object.
   * @param XmlElement An XML data structure
   * @return OrderAdjustment 
   */
  public static function fromXML($struct) {
    $a = new Address();
    $a->email = $struct->children[0]->content;
    $a->address1 = $struct->children[1]->content;
    $a->address2 = $struct->children[2]->content;
    $a->companyName = $struct->children[3]->content;
    $a->contactName = $struct->children[4]->content;
    $a->phone = $struct->children[5]->content;
    $a->fax = $struct->children[6]->content;
    $a->countryCode = $struct->children[7]->content;
    $a->city = $struct->children[8]->content;
    $a->region = $struct->children[9]->content;
    $a->postalCode = $struct->children[10]->content;
    return $a;
  }
}

/**
 * A helper class for parsing XML messages posted by Google Checkout.
 *
 * @package GoogleCheckoutServer
 */
class XmlElement {
  var $name;
  var $attributes;
  var $content;
  var $children;
}

/**
 * The purchaser's marketing preferences, e.g. can you send them email?
 *
 * @package GoogleCheckoutServer
 */
class MarketingPreferences {
  /**
   * Whether or not this user may be contacted via email.
   * @var boolean
   */
  public $emailAllowed;
  /**
   * Converts XML into a proper object.
   * @param XmlElement An XML data structure
   * @return MarketingPreferences
   * @todo fix
   */
  public static function fromXML($struct) {
    $this->emailAllowed = $struct->children[0]->content;
  }
  /**
   * Converts an array into a proper object.
   * @param Array An associative array, typically a $_POST array.
   * @return MarketingPreferences
   */
  public static function fromPost($array) {
    $m = new MarketingPreferences();
    $m->emailAllowed = $array['buyer-marketing-preferences_email-allowed'];
    return $m;
  }
}

/**
 * Details about any adjustments made to an order.
 *
 * @package GoogleCheckoutServer
 */
class OrderAdjustment {
  /**
   * The merchant codes.
   * @todo Define what merchant codes are.
   * @var string
   */
  public $merchantCodes;
  /**
   * The total tax applied to the order.
   * @var float
   */
  public $totalTax;
  /**
   * The amount the order has been adjusted.
   * @var float
   */
  public $adjustmentTotal;
  /**
   * Converts XML into a proper object.
   * @param XmlElement An XML data structure
   * @return OrderAdjustment 
   */
  public static function fromXML($struct) {
    $m = new OrderAdjustment();
    $m->emailAllowed = $struct->children[0]->content;
    $m->totalTax = $struct->children[1]->content;
    $m->adjustmentTotal = $struct->children[2]->content;
    return $m;
  }
  /**
   * Converts an array into a proper object.
   * @param Array An associative array, typically a $_POST array.
   * @return OrderAdjustment 
   */
  public static function fromPost($array) {
    $m = new OrderAdjustment();
    $m->emailAllowed = $array['buyer-marketing-preferences_email-allowed'];
    $m->totalTax = $array['order-adjustment_total-tax'];
    $m->adjustmentTotal = $array['order-adjustment_adjustment-total'];
  }
}

/**
 * This class is responsible for sending messages to Google Checkout. It
 * is utilized by GoogleCheckoutServer to issue instructions in response
 * to various events.
 *
 * @package GoogleCheckoutServer
 */
class GoogleCheckoutClient {
  private $logger;
  function __construct() {
    $this->logger = &Log::factory('file', CHECKOUT_LOG_FILE , 'GOOGLE_CHECKOUT');
  }
  /**
   * This method sends a message to the Google Checkout Server.
   * 
   * @param string The raw XML you want to send to Google. 
   * @return string The raw XML that is returned by Google.
   */
  function send($message) {
    $post_url = CHECKOUT_SERVER."cws/v2/Merchant/".MERCHANT_ID."/request";
    $this->logger->log("Sending request to $post_url.");
    $old_log_level = error_reporting(E_ERROR);
    $request =& new HTTP_Request($post_url);
    $request->setMethod(HTTP_REQUEST_METHOD_POST);
    $request->setBasicAuth(MERCHANT_ID, MERCHANT_KEY);
    $request->addHeader("Content-type","application/xml");
    $request->addHeader("Accept","application/xml");
    $request->addRawPostData($message);
    $response = $request->sendRequest();
    error_reporting($old_log_level);
    $this->logger->log("Request sent.");
    if (PEAR::isError($response)) {
      $this->logger->log("There was an error (".$request->getResponseCode()."): " . $response->getMessage());
    } else {
      $this->logger->log("Request successful: " . $request->getResponseBody());
      return $request->getResponseBody();
    }
  }
}
?>